---
id: createEntityAdapter
title: createEntityAdapter
sidebar_label: createEntityAdapter
hide_title: true
---

&nbsp;

# `createEntityAdapter`

## Overview

A function that generates a set of prebuilt reducers and selectors for performing CRUD operations on a [normalized state structure](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape) containing instances of a particular type of data object. These reducer functions may be passed as case reducers to `createReducer` and `createSlice`. They may also be used as "mutating" helper functions inside of `createReducer` and `createSlice`.

This API was ported from [the `@ngrx/entity` library](https://ngrx.io/guide/entity) created by the NgRx maintainers, but has been significantly modified for use with Redux Toolkit. We'd like to thank the NgRx team for originally creating this API and allowing us to port and adapt it for our needs.

:::note
The term "Entity" is used to refer to a unique type of data object in an application. For example, in a blogging application, you might have `User`, `Post`, and `Comment` data objects, with many instances of each being stored in the client and persisted on the server. `User` is an "entity" - a unique type of data object that the application uses. Each unique instance of an entity is assumed to have a unique ID value in a specific field.

As with all Redux logic, [_only_ plain JS objects and arrays should be passed in to the store - **no class instances!**](https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions)

For purposes of this reference, we will use `Entity` to refer to the specific data type that is being managed by a copy of the reducer logic in a specific portion of the Redux state tree, and `entity` to refer to a single instance of that type. Example: in `state.users`, `Entity` would refer to the `User` type, and `state.users.entities[123]` would be a single `entity`.
:::

The methods generated by `createEntityAdapter` will all manipulate an "entity state" structure that looks like:

```js
{
  // The unique IDs of each item. Must be strings or numbers
  ids: []
  // A lookup table mapping entity IDs to the corresponding entity objects
  entities: {
  }
}
```

`createEntityAdapter` may be called multiple times in an application. If you are using it with plain JavaScript, you may be able to reuse a single adapter definition with multiple entity types if they're similar enough (such as all having an `entity.id` field). For [TypeScript usage](../usage/usage-with-typescript.md#createentityadapter), you will need to call `createEntityAdapter` a separate time for each distinct `Entity` type, so that the type definitions are inferred correctly.

Sample usage:

```ts
import {
  createEntityAdapter,
  createSlice,
  configureStore,
} from '@reduxjs/toolkit'

type Book = { bookId: string; title: string }

const booksAdapter = createEntityAdapter({
  // Assume IDs are stored in a field other than `book.id`
  selectId: (book: Book) => book.bookId,
  // Keep the "all IDs" array sorted based on book titles
  sortComparer: (a, b) => a.title.localeCompare(b.title),
})

const booksSlice = createSlice({
  name: 'books',
  initialState: booksAdapter.getInitialState(),
  reducers: {
    // Can pass adapter functions directly as case reducers.  Because we're passing this
    // as a value, `createSlice` will auto-generate the `bookAdded` action type / creator
    bookAdded: booksAdapter.addOne,
    booksReceived(state, action) {
      // Or, call them as "mutating" helpers in a case reducer
      booksAdapter.setAll(state, action.payload.books)
    },
  },
})

const store = configureStore({
  reducer: {
    books: booksSlice.reducer,
  },
})

type RootState = ReturnType<typeof store.getState>

console.log(store.getState().books)
// { ids: [], entities: {} }

// Can create a set of memoized selectors based on the location of this entity state
const booksSelectors = booksAdapter.getSelectors<RootState>(
  (state) => state.books,
)

// And then use the selectors to retrieve values
const allBooks = booksSelectors.selectAll(store.getState())
```

## Parameters

`createEntityAdapter` accepts a single options object parameter, with two optional fields inside.

### `selectId`

A function that accepts a single `Entity` instance, and returns the value of whatever unique ID field is inside. If not provided, the default implementation is `entity => entity.id`. If your `Entity` type keeps its unique ID values in a field other than `entity.id`, you **must** provide a `selectId` function.

### `sortComparer`

A callback function that accepts two `Entity` instances, and should return a standard `Array.sort()` numeric result (1, 0, -1) to indicate their relative order for sorting.

If provided, the `state.ids` array will be kept in sorted order based on comparisons of the entity objects, so that mapping over the IDs array to retrieve entities by ID should result in a sorted array of entities.

If not provided, the `state.ids` array will not be sorted, and no guarantees are made about the ordering. In other words, `state.ids` can be expected to behave like a standard Javascript array.

Note that sorting only kicks in when state is changed via one of the CRUD functions below (for example, `addOne()`, `updateMany()`).

## Return Value

A "entity adapter" instance. An entity adapter is a plain JS object (not a class) containing the generated reducer functions, the original provided `selectId` and `sortComparer` callbacks, a method to generate an initial "entity state" value, and functions to generate a set of globalized and non-globalized memoized selector functions for this entity type.

The adapter instance will include the following methods (additional referenced TypeScript types included):

```ts no-transpile
export type EntityId = number | string

export type Comparer<T> = (a: T, b: T) => number

export type IdSelector<T> = (model: T) => EntityId

export type Update<T> = { id: EntityId; changes: Partial<T> }

export interface EntityState<T> {
  ids: EntityId[]
  entities: Record<EntityId, T>
}

export interface EntityDefinition<T> {
  selectId: IdSelector<T>
  sortComparer: false | Comparer<T>
}

export interface EntityStateAdapter<T> {
  addOne<S extends EntityState<T>>(state: S, entity: T): S
  addOne<S extends EntityState<T>>(state: S, action: PayloadAction<T>): S

  addMany<S extends EntityState<T>>(state: S, entities: T[]): S
  addMany<S extends EntityState<T>>(state: S, entities: PayloadAction<T[]>): S

  setOne<S extends EntityState<T>>(state: S, entity: T): S
  setOne<S extends EntityState<T>>(state: S, action: PayloadAction<T>): S

  setMany<S extends EntityState<T>>(state: S, entities: T[]): S
  setMany<S extends EntityState<T>>(state: S, entities: PayloadAction<T[]>): S

  setAll<S extends EntityState<T>>(state: S, entities: T[]): S
  setAll<S extends EntityState<T>>(state: S, entities: PayloadAction<T[]>): S

  removeOne<S extends EntityState<T>>(state: S, key: EntityId): S
  removeOne<S extends EntityState<T>>(state: S, key: PayloadAction<EntityId>): S

  removeMany<S extends EntityState<T>>(state: S, keys: EntityId[]): S
  removeMany<S extends EntityState<T>>(
    state: S,
    keys: PayloadAction<EntityId[]>,
  ): S

  removeAll<S extends EntityState<T>>(state: S): S

  updateOne<S extends EntityState<T>>(state: S, update: Update<T>): S
  updateOne<S extends EntityState<T>>(
    state: S,
    update: PayloadAction<Update<T>>,
  ): S

  updateMany<S extends EntityState<T>>(state: S, updates: Update<T>[]): S
  updateMany<S extends EntityState<T>>(
    state: S,
    updates: PayloadAction<Update<T>[]>,
  ): S

  upsertOne<S extends EntityState<T>>(state: S, entity: T): S
  upsertOne<S extends EntityState<T>>(state: S, entity: PayloadAction<T>): S

  upsertMany<S extends EntityState<T>>(state: S, entities: T[]): S
  upsertMany<S extends EntityState<T>>(
    state: S,
    entities: PayloadAction<T[]>,
  ): S
}

export interface EntitySelectors<T, V> {
  selectIds: (state: V) => EntityId[]
  selectEntities: (state: V) => Record<EntityId, T>
  selectAll: (state: V) => T[]
  selectTotal: (state: V) => number
  selectById: (state: V, id: EntityId) => T | undefined
}

export interface EntityAdapter<T> extends EntityStateAdapter<T> {
  selectId: IdSelector<T>
  sortComparer: false | Comparer<T>
  getInitialState(): EntityState<T>
  getInitialState<S extends object>(state: S): EntityState<T> & S
  getSelectors(): EntitySelectors<T, EntityState<T>>
  getSelectors<V>(
    selectState: (state: V) => EntityState<T>,
  ): EntitySelectors<T, V>
}
```

### CRUD Functions

The primary content of an entity adapter is a set of generated reducer functions for adding, updating, and removing entity instances from an entity state object:

- `addOne`: accepts a single entity, and adds it if it's not already present.
- `addMany`: accepts an array of entities or an object in the shape of `Record<EntityId, T>`, and adds them if not already present.
- `setOne`: accepts a single entity and adds or replaces it
- `setMany`: accepts an array of entities or an object in the shape of `Record<EntityId, T>`, and adds or replaces them.
- `setAll`: accepts an array of entities or an object in the shape of `Record<EntityId, T>`, and replaces all existing entities with the values in the array.
- `removeOne`: accepts a single entity ID value, and removes the entity with that ID if it exists.
- `removeMany`: accepts an array of entity ID values, and removes each entity with those IDs if they exist.
- `removeAll`: removes all entities from the entity state object.
- `updateOne`: accepts an "update object" containing an entity ID and an object containing one or more new field values to update inside a `changes` field, and performs a shallow update on the corresponding entity.
- `updateMany`: accepts an array of update objects, and performs shallow updates on all corresponding entities.
- `upsertOne`: accepts a single entity. If an entity with that ID exists, it will perform a shallow update and the specified fields will be merged into the existing entity, with any matching fields overwriting the existing values. If the entity does not exist, it will be added.
- `upsertMany`: accepts an array of entities or an object in the shape of `Record<EntityId, T>` that will be shallowly upserted.

:::info Should I add, set or upsert my entity?

All three options will insert _new_ entities into the list. However they differ in how they handle entities that already exist. If an entity **already exists**:

- `addOne` and `addMany` will do nothing with the new entity
- `setOne` and `setMany` will completely replace the old entity with the new one. This will also get rid of any properties on the entity that are not present in the new version of said entity.
- `upsertOne` and `upsertMany` will do a shallow copy to merge the old and new entities overwriting existing values, adding any that were not there and not touching properties not provided in the new entity.

:::

Each method has a signature that looks like:

```ts no-transpile
;(state: EntityState<T>, argument: TypeOrPayloadAction<Argument<T>>) =>
  EntityState<T>
```

In other words, they accept a state that looks like `{ids: [], entities: {}}`, and calculate and return a new state.

These CRUD methods may be used in multiple ways:

- They may be passed as case reducers directly to `createReducer` and `createSlice`.
- They may be used as "mutating" helper methods when called manually, such as a separate hand-written call to `addOne()` inside of an existing case reducer, if the `state` argument is actually an Immer `Draft` value.
- They may be used as immutable update methods when called manually, if the `state` argument is actually a plain JS object or array.

:::note
These methods do _not_ have corresponding Redux actions created - they are just standalone reducers / update logic. **It is entirely up to you to decide where and how to use these methods!** Most of the time, you will want to pass them to `createSlice` or use them inside another reducer.
:::

Each method will check to see if the `state` argument is an Immer `Draft` or not. If it is a draft, the method will assume that it's safe to continue mutating that draft further. If it is not a draft, the method will pass the plain JS value to Immer's `createNextState()`, and return the immutably updated result value.

The `argument` may be either a plain value (such as a single `Entity` object for `addOne()` or an `Entity[]` array for `addMany()`, or a `PayloadAction` action object with that same value as `action.payload`. This enables using them as both helper functions and reducers.

> **Note on shallow updates:** `updateOne`, `updateMany`, `upsertOne`, and `upsertMany` only perform shallow updates in a mutable manner. This means that if your update/upsert consists of an object that includes nested properties, the value of the incoming change will overwrite the **entire** existing nested object. This may be unintended behavior for your application. As a general rule, these methods are best used with [normalized data](../usage/usage-guide.md#managing-normalized-data) that _do not_ have nested properties.

### `getInitialState`

Returns a new entity state object like `{ids: [], entities: {}}`.

It accepts an optional object as an argument. The fields in that object will be merged into the returned initial state value. For example, perhaps you want your slice to also track some loading state:

```js
const booksSlice = createSlice({
  name: 'books',
  initialState: booksAdapter.getInitialState({
    loading: 'idle',
  }),
  reducers: {
    booksLoadingStarted(state, action) {
      // Can update the additional state field
      state.loading = 'pending'
    },
  },
})
```

You can also pass in an array of entities or a `Record<EntityId, T>` object to pre-populate the initial state with some entities:

```js
const booksSlice = createSlice({
  name: 'books',
  initialState: booksAdapter.getInitialState(
    {
      loading: 'idle',
    },
    [
      { id: 'a', title: 'First' },
      { id: 'b', title: 'Second' },
    ],
  ),
  reducers: {},
})
```

This is equivalent to calling:

```js
const initialState = booksAdapter.getInitialState({
  loading: 'idle',
})

const prePopulatedState = booksAdapter.setAll(initialState, [
  { id: 'a', title: 'First' },
  { id: 'b', title: 'Second' },
])
```

The first parameter can be `undefined` if no additional properties are needed.

### Selector Functions

The entity adapter will contain a `getSelectors()` function that returns a set of selectors that know how to read the contents of an entity state object:

- `selectIds`: returns the `state.ids` array.
- `selectEntities`: returns the `state.entities` lookup table.
- `selectAll`: maps over the `state.ids` array, and returns an array of entities in the same order.
- `selectTotal`: returns the total number of entities being stored in this state.
- `selectById`: given the state and an entity ID, returns the entity with that ID or `undefined`.

Each selector function will be created using the `createSelector` function from Reselect, to enable memoizing calculation of the results.

:::tip

The `createSelector` instance used can be replaced, by passing it as part of the options object (second parameter):

```js
import {
  createDraftSafeSelectorCreator,
  weakMapMemoize,
} from '@reduxjs/toolkit'

const createWeakMapDraftSafeSelector =
  createDraftSafeSelectorCreator(weakMapMemoize)

const simpleSelectors = booksAdapter.getSelectors(undefined, {
  createSelector: createWeakMapDraftSafeSelector,
})

const globalizedSelectors = booksAdapter.getSelectors((state) => state.books, {
  createSelector: createWeakMapDraftSafeSelector,
})
```

If no instance is passed, it will default to [`createDraftSafeSelector`](./createSelector.mdx#createdraftsafeselector).

:::

Because selector functions are dependent on knowing where in the state tree this specific entity state object is kept, `getSelectors()` can be called in two ways:

- If called without any arguments (or with undefined as the first parameter), it returns an "unglobalized" set of selector functions that assume their `state` argument is the actual entity state object to read from.
- It may also be called with a selector function that accepts the entire Redux state tree and returns the correct entity state object.

For example, the entity state for a `Book` type might be kept in the Redux state tree as `state.books`. You can use `getSelectors()` to read from that state in two ways:

```js
const store = configureStore({
  reducer: {
    books: booksReducer,
  },
})

const simpleSelectors = booksAdapter.getSelectors()
const globalizedSelectors = booksAdapter.getSelectors((state) => state.books)

// Need to manually pass the correct entity state object in to this selector
const bookIds = simpleSelectors.selectIds(store.getState().books)

// This selector already knows how to find the books entity state
const allBooks = globalizedSelectors.selectAll(store.getState())
```

## Notes

### Applying Multiple Updates

If `updateMany()` is called with multiple updates targeted to the same ID, they will be merged into a single update, with later updates overwriting the earlier ones.

For both `updateOne()` and `updateMany()`, changing the ID of one existing entity to match the ID of a second existing entity will cause the first to replace the second completely.

Additionally, if there is no item for that ID, the update will be silently ignored.

## Examples

Exercising several of the CRUD methods and selectors:

```js
import {
  createEntityAdapter,
  createSlice,
  configureStore,
} from '@reduxjs/toolkit'

// Since we don't provide `selectId`, it defaults to assuming `entity.id` is the right field
const booksAdapter = createEntityAdapter({
  // Keep the "all IDs" array sorted based on book titles
  sortComparer: (a, b) => a.title.localeCompare(b.title),
})

const booksSlice = createSlice({
  name: 'books',
  initialState: booksAdapter.getInitialState({
    loading: 'idle',
  }),
  reducers: {
    // Can pass adapter functions directly as case reducers.  Because we're passing this
    // as a value, `createSlice` will auto-generate the `bookAdded` action type / creator
    bookAdded: booksAdapter.addOne,
    booksLoading(state, action) {
      if (state.loading === 'idle') {
        state.loading = 'pending'
      }
    },
    booksReceived(state, action) {
      if (state.loading === 'pending') {
        // Or, call them as "mutating" helpers in a case reducer
        booksAdapter.setAll(state, action.payload)
        state.loading = 'idle'
      }
    },
    bookUpdated: booksAdapter.updateOne,
  },
})

const { bookAdded, booksLoading, booksReceived, bookUpdated } =
  booksSlice.actions

const store = configureStore({
  reducer: {
    books: booksSlice.reducer,
  },
})

// Check the initial state:
console.log(store.getState().books)
// {ids: [], entities: {}, loading: 'idle' }

const booksSelectors = booksAdapter.getSelectors((state) => state.books)

store.dispatch(bookAdded({ id: 'a', title: 'First' }))
console.log(store.getState().books)
// {ids: ["a"], entities: {a: {id: "a", title: "First"}}, loading: 'idle' }

store.dispatch(bookUpdated({ id: 'a', changes: { title: 'First (altered)' } }))
store.dispatch(booksLoading())
console.log(store.getState().books)
// {ids: ["a"], entities: {a: {id: "a", title: "First (altered)"}}, loading: 'pending' }

store.dispatch(
  booksReceived([
    { id: 'b', title: 'Book 3' },
    { id: 'c', title: 'Book 2' },
  ]),
)

console.log(booksSelectors.selectIds(store.getState()))
// "a" was removed due to the `setAll()` call
// Since they're sorted by title, "Book 2" comes before "Book 3"
// ["c", "b"]

console.log(booksSelectors.selectAll(store.getState()))
// All book entries in sorted order
// [{id: "c", title: "Book 2"}, {id: "b", title: "Book 3"}]
```
